// MB Note Quantize 1.1
// by mabian 08.10.2009

desc:MB Note Quantize (v1.1)

slider1:0<0,16,1{0:All, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}>Midi channel
slider2:4<0,10,1{0:4 beats = measure, 1:2 beats = half measure, 2:1 beat = 1/4 notes, 3:1/2 beat = 1/8 notes, 4:1/4 beat = 1/16 notes, 5:1/8 beat = 1/32 notes, 6:1/16 beat = 1/64 notes, 7: 4/3 beat = 1/2 triplets, 8: 2/3 beat = 1/4 triplets, 9:1/3 beat = 1/8 triplets, 10: 1/6 beat = 1/16 triplets, 11: 1/12 beat = 1/32 triplets, 12: 1/24 beat = 1/64 triplets}>Quantize Interval
slider3:0<-100,100>Grid offset (msec)
slider4:0<-100,100,1>Swing %
slider5:100<0,100>Strength %
slider6:0<0,100,1>Window min %
slider7:100<0,100,1>Window max %
slider8:0<0,100,1>Humanize %
slider9:2<0,8,0.5>Delay (beats, at least 2 * quantize interval)
slider10:<0,10000,1>Calculated delay (msec, read-only)


in_pin:none
out_pin:none

// srate = project sample rate
// tempo = current BPM tempo
// samplesblock = samples per block

@init
    NOTE_ON = 9;    // note on MIDI message code
    NOTE_OFF = 8;   // note off MIDI message code
    AFTERTCH = 10;  // poly aftertouch MIDI message code

    buffer = 0;     // init buffer
    MSG_BUF = 128 * 16;  // msg buffer start location (first 128 * 16 bytes are for timing offset for each note/channel).
    memset(0, 0, MSG_BUF);    // clear note timing offset buffer
    n_buf = 0;      // index into buffer 

    beat_start = 1;     // start beat of project
    cur_delay = -1;      // start val of quantize delay
    
@slider
    (slider2 == 0) ? (quantize_mult = 4);
    (slider2 == 1) ? (quantize_mult = 2);
    (slider2 == 2) ? (quantize_mult = 1);
    (slider2 == 3) ? (quantize_mult = 1/2);
    (slider2 == 4) ? (quantize_mult = 1/4);
    (slider2 == 5) ? (quantize_mult = 1/8);
    (slider2 == 6) ? (quantize_mult = 1/16);
    (slider2 == 7) ? (quantize_mult = 4/3);
    (slider2 == 8) ? (quantize_mult = 2/3);
    (slider2 == 9) ? (quantize_mult = 1/3);
    (slider2 == 10) ? (quantize_mult = 1/6);
    (slider2 == 11) ? (quantize_mult = 1/12);
    (slider2 == 12) ? (quantize_mult = 1/24);
    
    cur_delay = -1;

@block
    // calculate various useful amounts in samples
    // supposing they stay constant for all block
    samples_per_beat = (srate * 60) / tempo;        // samples in a beat
    samples_delay = (samples_per_beat * slider9);   // how many samples events are sent early
    samples_offset = (srate * slider3) / 1000;      // timing offset for grid

    // update read only slider
    (samples_delay != cur_delay) ?
    (
        cur_delay = samples_delay;
        slider10 = (samples_delay / srate) * 1000;
        sliderchange(slider10);
    );

    ((play_state == 1) || (play_state == 5)) ?
    (

        // window boundaries - 0 = none, 100% = quantize interval / 2
        minwin = (quantize_mult * slider6) / 200;     // min window distance
        maxwin = (quantize_mult * slider7) / 200;     // min window distance

        while
        (
            midirecv(mpos, msg1, msg23) ?
            (
                // get message components
                msg = (msg1 / 16) & $x0f;           // message type nibble
                channel = msg1 & $x0f;              // channel nibble
                note_num = msg23 & $x7f;            // note num
                velocity = (msg23 / 256) & $x7f;    // velocity
    
                ((slider1 == 0) || (slider1 == channel + 1)) ?      // message filter
                (
                    ((msg == NOTE_ON) && (velocity > 0)) ?      // real note on only - velocity must be > 0
                    (
                        // exact position of where this note should play if unquantized, compensated by grid offset
                        beat_cur = beat_position + ((mpos + samples_delay - samples_offset) / samples_per_beat);

                        // exact position of where this note should play if unquantized
                        beat_real = beat_position + ((mpos + samples_delay) / samples_per_beat);

                        // computer nearest lower and upper grid boundaries
                        quantized_lo = (floor((beat_cur - beat_start) / quantize_mult) * quantize_mult) + beat_start;       // nearest lower           
                        quantized_hi = ((floor((beat_cur - beat_start) / quantize_mult) + 1) * quantize_mult) + beat_start; // nearest higher

                        (quantized_hi - beat_cur < beat_cur - quantized_lo) ?       // find the nearest grid boundary
                        (
                            distance = quantized_hi - beat_cur;
                            beat_cur = quantized_hi;
                        )
                        :
                        (
                            distance = beat_cur - quantized_lo;
                            beat_cur = quantized_lo;
                        );

                        ((distance >= minwin) && (distance <= maxwin)) ?            // if within window, process
                        (                     
                            ((floor((beat_cur - beat_start) / quantize_mult) & 1) > 0) ?    // even or odd multiple of interval (for swing!)
                            (
                                beat_cur = beat_cur + (quantize_mult / 2) * slider4 / 100;
                            );

                            // delta for note position from current pos to quantized pos
                            new_mpos =
                            (
                                (
                                    (
                                        (beat_cur - beat_real) * samples_per_beat
                                    )
                                    + samples_offset
                                )
                                * (slider5 / 100)
                            );
                        )
                        :
                        (
                            new_mpos = 0;       // just leave note as it is
                        );

                        (slider8 > 0) ?        // humanize (simple symmetric rand as of now!!!
                        (
                            new_mpos += ((quantize_mult * rand(slider8) / 100) - ((quantize_mult / 2) * slider8 / 100)) * samples_per_beat;
                        );

                        mpos += new_mpos;
                        buffer[(128 * channel) + note_num] = new_mpos;        // offset to be used for next note off and aftertouch on same note
                    );
    
                    (msg == AFTERTCH) ?                             // simply move aftertouch events
                    (
                        mpos += buffer[(128 * channel) + note_num];
                    );
        
                    ((msg == NOTE_OFF) || ((msg == NOTE_ON) && (velocity == 0))) ?      // simply move note off events
                    (
                        mpos += buffer[(128 * channel) + note_num];
                    );
                ); 

                mpos += samples_delay;

                (mpos >= 0) ?
                (
                  buffer[MSG_BUF+n_buf] = mpos;
                  buffer[MSG_BUF+n_buf+1] = msg1;
                  buffer[MSG_BUF+n_buf+2] = msg23;
                  n_buf += 3;
                );
            );
            
            msg1;       // exit check
        );
    
    // Find everything in the buffer that is due
    // to be played back in this block.
        i = 0;
        while
        (
            (i < n_buf) ?
            (
                mpos = buffer[MSG_BUF + i];
            
                (mpos < samplesblock) ?
                (
                    midisend(mpos, buffer[MSG_BUF+i+1], buffer[MSG_BUF+i+2]);
                    memcpy(MSG_BUF+i, MSG_BUF+i+3, n_buf-i);
                    n_buf -= 3;
                ) :
                (
                    buffer[MSG_BUF+i] -= samplesblock;
                    i += 3;
                );
            );
        );
    );
